using System;
using System.Collections.Generic;
using System.Text;
//using ApplicationCore.EnzymeProcessingDataSetTableAdapters;
using System.Drawing;
using System.Linq;

namespace ApplicationCore
{
    public class BatchLayout
    {
        private string[,] layout;
        //private BatchCompilationTableAdapter bcta;
        private string batchName;
        //private EnzymeProcessingDataSet.BatchesRow batchRow;

        /// <summary>
        /// Gets or sets a function that returns the password.
        /// </summary>
        public Func<EnzymeDBDataContext> DataContextAccessor { get; set; }


        public string BatchName
        {
            get { return batchName; }
        }

        public string[,] Layout
        {
            get { return layout; }
        }

        //private EnzymeProcessingDataSet data;

        //public EnzymeProcessingDataSet Data
        //{
        //    get { return data; }
        //    set { data = value; }
        //}

        public BatchLayout()
        {
            //NewLayout();
        }

        public int PlateRowCount { get; private set; }

        public int PlateColumnCount { get; private set; }

        public int LayoutRowCount
        {
            get
            {
                return layout.GetLength(0);
            }
        }

        public int LayoutColumnCount
        {
            get
            {
                return layout.GetLength(1);
            }
        }

        public bool IsEmpty { get { return layout.GetLength(0) == 0; } }

        public string LayoutType { get; private set; }

        //obligate
        //public void LoadLayout(EnzymeProcessingDataSet.BatchesRow dataRow)
        //{
        //    //this.batchRow = dataRow;
        //    this.batchName = dataRow.Name + "-" + dataRow.Number + "-" + dataRow.Date.ToShortDateString();
        //    NewLayout();
        //    EnzymeProcessingDataSet.BatchCompilationRow[] rows;
        //    StringBuilder sb = new StringBuilder();
        //    sb.AppendFormat("BatchName_Fk = '{0}' and BatchNumber_Fk = '{1}'", dataRow.Name, dataRow.Number);
        //    rows = (EnzymeProcessingDataSet.BatchCompilationRow[])data.BatchCompilation.Select(sb.ToString());
        //    foreach (EnzymeProcessingDataSet.BatchCompilationRow r in rows)
        //    {
        //        if (r.BatchDate_Fk == dataRow.Date)
        //        {
        //            if (r.Experiment_Fk == "EB")
        //                this.layout[r.Row, r.Column] = "EB";
        //            else if (r.Experiment_Fk == "?")
        //                this.layout[r.Row, r.Column] = "?";
        //            else
        //                this.layout[r.Row, r.Column] = r.Experiment_Fk + "-" + r.Sample_Fk + "-" + r.Aliquot_Fk;
        //        }

        //    }

        //}

        private EnzymeDBDataContext _currentContext;
        private BatchView _currentBatchView;
        public void LoadLayout(BatchView bview)
        {
            if (bview == null)
            {
                layout = new string[0, 0];
                this.batchName = "";
                PlateColumnCount = 0;
                PlateRowCount = 0;
                return;
            }

            if (DataContextAccessor == null)
                throw new InvalidOperationException("Can't process operation without data context");
            _currentBatchView = bview;
            if (_currentContext != null)
                _currentContext.Dispose();
            _currentContext = DataContextAccessor();

            LayoutType = bview.LayoutType;
            this.batchName = string.Join("-", bview.Name, bview.Number, bview.Date.ToShortDateString());
            PlateRowCount = bview.RowCount;
            PlateColumnCount = bview.ColumnCount;
            NewLayout();

            BatchView bv = _currentContext.BatchViews.SingleOrDefault(x => x.UId == bview.UId); //get the batch from teh database
            if (bv != null)
            {
                _colorCache.Clear();
                _controlCache.Clear();
                _sampleCache.Clear();
                _freshWeightCache.Clear();
                LayoutType = bv.LayoutType;
                this.batchName = string.Join("-", bv.Name, bv.Number, bv.Date.ToShortDateString());
                PlateRowCount = bv.RowCount;
                PlateColumnCount = bv.ColumnCount;
                NewLayout();
                var cells = from x in _currentContext.BatchCompilationViews where x.BatchUId == bv.UId select x;
                foreach (BatchCompilationView r in cells)
                {
                    if (r.Experiment_Fk == "EB" || r.Experiment_Fk == "?")
                        this.layout[r.Row, r.Column] = r.Experiment_Fk;
                    else
                        this.layout[r.Row, r.Column] = string.Join("-", r.Experiment_Fk, r.Sample_Fk, r.Aliquot_Fk);

                    Entry2Color(this.layout[r.Row, r.Column]); //pre-load color mapping and builds cache
                }
            }
        }

        //public void SaveLayout(EnzymeProcessingDataSet.BatchesRow dataRow)
        //{
        //    if (dataRow.RowState == System.Data.DataRowState.Detached)
        //    {
        //        (dataRow.Table as EnzymeProcessingDataSet.BatchesDataTable).AddBatchesRow(dataRow);

        //    }

        //    for (int i = 0; i < layout.GetLength(0); i++)
        //    {
        //        for (int j = 0; j < layout.GetLength(1); j++)
        //        {
        //            string updateString = this.layout[i, j];
        //            if (!String.IsNullOrEmpty(updateString))
        //            {



        //                string[] udateArray = updateString.Split('-');
        //                EnzymeProcessingDataSet.BatchCompilationRow br = data.BatchCompilation.FindByBatchName_FkBatchNumber_FkBatchDate_FkRowColumn(dataRow.Name, dataRow.Number, dataRow.Date, i, j);
        //                if (br != null)
        //                {
        //                    br.BeginEdit();
        //                    if (IsExtractionBuffer(updateString))
        //                    {
        //                        br.Experiment_Fk = "EB";
        //                        br.Sample_Fk = 1;
        //                        br.Aliquot_Fk = 1;
        //                    }
        //                    else if (IsMissingAliquot(updateString))
        //                    {
        //                        br.Experiment_Fk = "?";
        //                        br.Sample_Fk = 1;
        //                        br.Aliquot_Fk = 1;
        //                    }

        //                    else
        //                    {

        //                        br.Experiment_Fk = udateArray[0];
        //                        br.Sample_Fk = Convert.ToInt32(udateArray[1]);
        //                        br.Aliquot_Fk = Convert.ToInt32(udateArray[2]);

        //                    }
        //                    br.EndEdit();
        //                }
        //                else
        //                {
        //                    br = data.BatchCompilation.NewBatchCompilationRow();
        //                    br.BatchName_Fk = dataRow.Name;
        //                    br.BatchNumber_Fk = dataRow.Number;
        //                    br.BatchDate_Fk = dataRow.Date;

        //                    if (updateString.ToLower() == "eb")
        //                    {
        //                        br.Experiment_Fk = "EB";
        //                        br.Sample_Fk = 1;
        //                        br.Aliquot_Fk = 1;
        //                    }
        //                    else if (IsMissingAliquot(updateString))
        //                    {
        //                        br.Experiment_Fk = "?";
        //                        br.Sample_Fk = 1;
        //                        br.Aliquot_Fk = 1;
        //                    }
        //                    else
        //                    {
        //                        br.Experiment_Fk = udateArray[0];
        //                        br.Sample_Fk = Convert.ToInt32(udateArray[1]);
        //                        br.Aliquot_Fk = Convert.ToInt32(udateArray[2]);
        //                    }


        //                    br.Row = i;
        //                    br.Column = j;
        //                    data.BatchCompilation.AddBatchCompilationRow(br);
        //                }
        //            }
        //            else
        //            {
        //                EnzymeProcessingDataSet.BatchCompilationRow br = data.BatchCompilation.FindByBatchName_FkBatchNumber_FkBatchDate_FkRowColumn(dataRow.Name, dataRow.Number, dataRow.Date, i, j);
        //                if (br != null && br.RowState != System.Data.DataRowState.Deleted)
        //                {
        //                    br.Delete();
        //                }
        //            }

        //        }
        //    }
        //    bcta.Update(data.BatchCompilation);

        //}

        public void SaveLayout()
        {
            for (int i = 0; i < LayoutRowCount; i++)
            {
                for (int j = 0; j < LayoutColumnCount; j++)
                {
                    string updateString = this.layout[i, j];
                    if (!String.IsNullOrEmpty(updateString))
                    {
                        string[] udateArray = updateString.Split('-');
                        BatchCompilation br = _currentContext.BatchCompilations.FirstOrDefault(x =>
                            x.BatchName_Fk == _currentBatchView.Name
                            && x.BatchNumber_Fk == _currentBatchView.Number
                            && x.BatchDate_Fk == _currentBatchView.Date
                            && x.Row == i
                            && x.Column == j);

                        if (br != null)
                        {
                            if (IsExtractionBuffer(updateString))
                            {
                                br.Experiment_Fk = "EB";
                                br.Sample_Fk = 1;
                                br.Aliquot_Fk = 1;
                            }
                            else if (IsMissingAliquot(updateString))
                            {
                                br.Experiment_Fk = "?";
                                br.Sample_Fk = 1;
                                br.Aliquot_Fk = 1;
                            }
                            else
                            {
                                br.Experiment_Fk = udateArray[0];
                                br.Sample_Fk = Convert.ToInt32(udateArray[1]);
                                br.Aliquot_Fk = Convert.ToInt32(udateArray[2]);
                            }
                        }
                        else
                        {
                            br = new BatchCompilation();
                            br.BatchName_Fk = _currentBatchView.Name;
                            br.BatchNumber_Fk = _currentBatchView.Number;
                            br.BatchDate_Fk = _currentBatchView.Date;

                            if (updateString.ToLower() == "eb")
                            {
                                br.Experiment_Fk = "EB";
                                br.Sample_Fk = 1;
                                br.Aliquot_Fk = 1;
                            }
                            else if (IsMissingAliquot(updateString))
                            {
                                br.Experiment_Fk = "?";
                                br.Sample_Fk = 1;
                                br.Aliquot_Fk = 1;
                            }
                            else
                            {
                                br.Experiment_Fk = udateArray[0];
                                br.Sample_Fk = Convert.ToInt32(udateArray[1]);
                                br.Aliquot_Fk = Convert.ToInt32(udateArray[2]);
                            }

                            br.Row = i;
                            br.Column = j;
                            _currentContext.BatchCompilations.InsertOnSubmit(br);
                        }
                    }
                    else
                    {
                        BatchCompilation br = _currentContext.BatchCompilations.FirstOrDefault(x =>
                            x.BatchName_Fk == _currentBatchView.Name
                            && x.BatchNumber_Fk == _currentBatchView.Number
                            && x.BatchDate_Fk == _currentBatchView.Date
                            && x.Row == i
                            && x.Column == j);
                        if (br != null)
                        {
                            _currentContext.BatchCompilations.DeleteOnSubmit(br);
                        }
                    }
                }
            }
            _currentContext.SubmitChanges();
        }

        public void NewLayout()
        {
            if (_currentBatchView.LayoutType == "SPLIT")
            {
                this.layout = new string[PlateRowCount / 2, PlateColumnCount]; //for a split layout the design is repeated in the lower half of the plate
            }
            else
            {
                this.layout = new string[PlateRowCount, PlateColumnCount];
            }
        }

        public bool ValidateAll()
        {
            List<string> usedEntries = new List<string>();
            bool valid = true;
            string error;
            for (int i = 0; i < LayoutRowCount; i++)
            {
                for (int j = 0; j < LayoutColumnCount; j++)
                {
                    if (!ValidateEntry(this.layout[i, j], out error, usedEntries))
                    {
                        OnValidationError(i, j, error);
                        valid = false;
                    }
                }
            }
            return valid;
        }

        public bool ValidateEntry(string entry, out string error, List<string> usedEntries = null)
        {
            error = string.Empty;
            bool valid = true;
            if (String.IsNullOrEmpty(entry))
            {
                error = "Empty strings are not allowed";
                valid = false;
            }
            else
            {
                if (!IsExtractionBuffer(entry) && !IsMissingAliquot(entry))
                {
                    if (usedEntries != null && usedEntries.Contains(entry.ToLower()))
                    {
                        error = "Double entry found.";
                        valid = false;
                    }
                    else
                    {
                        string[] ali = entry.Split('-');

                        if (usedEntries != null)
                        {
                            usedEntries.Add(entry.ToLower());
                        }

                        if (ali.Length != 3)
                        {
                            error = "Wrong input format. String like 'G001-1-1' expected";
                            valid = false;
                        }
                        else
                        {
                            int sample;
                            int aliquot;

                            if (!Int32.TryParse(ali[1], out sample))
                            {
                                error = "Wrong input format. Parsing of sample id failed.";
                                valid = false;
                            }
                            if (!Int32.TryParse(ali[2], out aliquot))
                            {
                                error = "Wrong input format. Parsing of aliquot number failed.";
                                valid = false;
                            }
                            object o = _currentContext.FreshWeightViews.FirstOrDefault(x => x.ExperimentId == ali[0] && x.Sample == sample && x.Aliquot == aliquot);
                            if (o == null)
                            {
                                error = "Data not found for " + entry;
                                valid = false;
                            }
                            string source;
                            if (AlreadyInUse(ali[0], sample, aliquot, out source))
                            {
                                error = "Aliquot already in use by " + source;
                                valid = false;
                            }
                        }
                    }
                }
            }
            return valid;
        }

        private bool AlreadyInUse(string experiment, int sample, int aliquot, out string source)
        {
            List<string> tmpErrors = new List<string>();

            tmpErrors.AddRange(
                _currentContext.BatchCompilationViews.Where(x => x.Experiment_Fk == experiment && x.Sample_Fk == sample && x.Aliquot_Fk == aliquot)
                .Where(x => !(x.BatchName == _currentBatchView.Name && x.BatchNumber == _currentBatchView.Number && x.BatchDate == _currentBatchView.Date))
                .ToList()   //otherwise we get an "Could not translate expression ... to SQL" exception
                .Select(x => string.Concat(x.BatchName, "-", x.BatchNumber, "-", x.BatchDate.ToShortDateString(), " in row ", x.Row + 1, ", column ", x.Column + 1))
            );

            source = string.Join(Environment.NewLine, tmpErrors);
            return tmpErrors.Count != 0;
        }

        public int[,] ControlMap()
        {
            FreshWeightView o;
            MaterialView p;
            List<int> columns = new List<int>();
            List<int> rows = new List<int>();
            for (int i = 0; i < layout.GetLength(0); i++)
            {
                for (int j = 0; j < layout.GetLength(1); j++)
                {
                    if (IsControl(layout[i, j], out o, out p))
                    {
                        rows.Add(i);
                        columns.Add(j);
                    }
                }
            }
            int[,] r = new int[rows.Count, 2];
            for (int i = 0; i < rows.Count; i++)
            {
                r[i, 0] = rows[i];
                r[i, 1] = columns[i];
            }

            return r;
        }

        public int[,] SampleMap()
        {
            FreshWeightView o;
            MaterialView p;

            List<int> columns = new List<int>();
            List<int> rows = new List<int>();
            for (int i = 0; i < layout.GetLength(0); i++)
            {
                for (int j = 0; j < layout.GetLength(1); j++)
                {
                    if (IsSample(layout[i, j], out o) && !IsControl(layout[i, j], out o, out p))
                    {
                        rows.Add(i);
                        columns.Add(j);
                    }
                }
            }
            int[,] r = new int[rows.Count, 2];
            for (int i = 0; i < rows.Count; i++)
            {
                r[i, 0] = rows[i];
                r[i, 1] = columns[i];
            }
            return r;

        }

        public int[,] BufferMap()
        {
            List<int> columns = new List<int>();
            List<int> rows = new List<int>();
            for (int i = 0; i < layout.GetLength(0); i++)
            {
                for (int j = 0; j < layout.GetLength(1); j++)
                {
                    if (IsExtractionBuffer(layout[i, j]))
                    {
                        rows.Add(i);
                        columns.Add(j);
                    }
                }
            }
            int[,] r = new int[rows.Count, 2];
            for (int i = 0; i < rows.Count; i++)
            {
                r[i, 0] = rows[i];
                r[i, 1] = columns[i];
            }
            return r;
        }


        Dictionary<string, double> _freshWeightCache = new Dictionary<string, double>();
        public double FreshWeight(int row, int column)
        {
            string entry = row + "_" + column;
            if (!_freshWeightCache.ContainsKey(entry))
            {
                if (_currentContext == null)
                    throw new InvalidOperationException("Can't process operation without data context");
                if (!IsExtractionBuffer(this.layout[row, column]) && !IsMissingAliquot(this.layout[row, column]))
                {
                    string[] spl = this.layout[row, column].Split('-');
                    int sample;
                    int aliquot;

                    if (!Int32.TryParse(spl[1], out sample))
                        throw new InvalidOperationException("Invalid freshweight definition. Can't get sample value.");
                    if (!Int32.TryParse(spl[2], out aliquot))
                        throw new InvalidOperationException("Invalid freshweight definition. Can't get aliquot value.");

                    FreshWeightView fwr;
                    IsSample(this.layout[row, column], out fwr);
                    if (fwr != null && fwr.Value.HasValue)
                    {
                        _freshWeightCache.Add(entry, fwr.Value.Value);
                        return fwr.Value.Value;
                    }
                    else
                    {
                        _freshWeightCache.Add(entry, default(double));
                        return default(double);
                    }
                }
                else
                {
                    _freshWeightCache.Add(entry, default(double));
                    return default(double);
                }
            }

            return _freshWeightCache[entry];
        }

        private Dictionary<string, Color> _colorCache = new Dictionary<string, Color>();

        public Color Entry2Color(string entry)
        {
            if (string.IsNullOrEmpty(entry))
            {
                return Color.White;
            }

            if (_colorCache.ContainsKey(entry))
            {
                return _colorCache[entry];
            }
            else
            {
                FreshWeightView o;
                MaterialView p;
                Color ret = Color.White;

                if (IsMissingAliquot(entry))
                {
                    ret = Color.Silver;
                }
                else if (IsExtractionBuffer(entry))
                {
                    ret = Color.FromArgb(116, 124, 216);
                }
                else if (IsControl(entry, out o, out p))
                {
                    ret = Color.LightGreen;
                }

                _colorCache.Add(entry, ret);
                return ret;
            }
        }

        private Dictionary<string, FreshWeightView> _sampleCache = new Dictionary<string, FreshWeightView>();
        private bool IsSample(string entry, out FreshWeightView fwRow)
        {
            fwRow = null;
            if (string.IsNullOrEmpty(entry))
            {
                return false;
            }
            if (_sampleCache.TryGetValue(entry, out fwRow))
            {
                return true;
            }
            if (_currentContext == null)
            {
                throw new InvalidOperationException("Can't process operation without data context");
            }

            string[] ali = entry.Split('-');
            if (ali.Length != 3)
            {
                return false;
            }

            string experimentId;
            int sample;
            int aliquot;

            experimentId = ali[0];
            if (!Int32.TryParse(ali[1], out sample))
            {
                return false;
            }
            if (!Int32.TryParse(ali[2], out aliquot))
            {
                return false;
            }
            FreshWeightView o = _currentContext.FreshWeightViews.SingleOrDefault(x => x.ExperimentId == experimentId && x.Sample == sample && x.Aliquot == aliquot);
            if (o == null)
            {
                return false;
            }
            else
            {
                _sampleCache.Add(entry, o);
                fwRow = o;
                return true;
            }
        }

        private bool IsExtractionBuffer(string entry)
        {
            if (String.IsNullOrEmpty(entry))
                return false;
            return entry.ToLower() == "eb";
        }

        private bool IsMissingAliquot(string entry)
        {
            if (String.IsNullOrEmpty(entry))
                return false;
            return entry == "?";
        }

        private Dictionary<string, Tuple<FreshWeightView, MaterialView>> _controlCache = new Dictionary<string, Tuple<FreshWeightView, MaterialView>>();
        internal bool IsControl(string entry, out FreshWeightView fwRow, out  MaterialView actRow)
        {
            fwRow = null;
            actRow = null;

            if (string.IsNullOrEmpty(entry))
            {
                return false;
            }

            Tuple<FreshWeightView, MaterialView> t = null;
            if (_controlCache.TryGetValue(entry, out t))
            {
                fwRow = t.Item1;
                actRow = t.Item2;
                return true;
            }
            if (DataContextAccessor == null)
                throw new InvalidOperationException("Can't process operation without data context");

            FreshWeightView f;
            if (IsSample(entry, out f))
            {
                if (!f.Material_Fk.HasValue)
                {
                    return false;
                }
                else
                {
                    MaterialView m = _currentContext.MaterialViews.Single(x => x.UId == f.Material_Fk.Value);
                    if (m.Name == "none")
                    {
                        return false;
                    }
                    _controlCache.Add(entry, new Tuple<FreshWeightView, MaterialView>(f, m));
                    actRow = m;
                    fwRow = f;
                    return true;
                }
            }
            else
            {
                return false;
            }
        }

        protected void OnValidationError(int row, int col, string msg)
        {
            if (this.Validation != null)
            {
                Validation(this, new ValidationErrorEventArgs(row, col, msg));
            }
        }

        public event EventHandler<ValidationErrorEventArgs> Validation;
    }
}
